// Copyright 2020 morgine.com. All rights reserved.

package handlers

import (
	"encoding/base64"
	"errors"
	"gitee.com/tugoer/psys/env"
	"gitee.com/tugoer/psys/models"
	"gitee.com/tugoer/psys/services"
	"github.com/gin-gonic/gin"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

var ErrLabelIsNotEmpty = errors.New("该标签下仍有图片")
var ErrLabelKeyIsExist = errors.New("key 已经存在")

type Directory struct {
	Accept bool
	Path   string
	Name   string
}

func GetImageDirectories(ctx *gin.Context, dirs []*Directory) {
	ctx.JSON(200, gin.H{"ls": dirs})
}

func GetDirectoryImages(ctx *gin.Context) {
	dir := ctx.Query("Dir")
	if dir != "" {
		fs, err := ioutil.ReadDir(dir)
		if err != nil {
			panic(err)
		}
		var files []interface{}
		for _, f := range fs {
			if !f.IsDir() {
				files = append(files, map[string]string{"Name": f.Name()})
			}
		}
		ctx.JSON(200, gin.H{"ls": files})
	} else {
		ctx.JSON(200, NewMessage("warning", "directory is empty"))
	}
}

func PsyTestImageService(ctx *gin.Context) {
	filename := ctx.Param("file") // 需通过 base64 加密, 且删除最后的空白符(即后面的所有'='号)"
	ext := filepath.Ext(filename)
	filename = strings.TrimSuffix(filename, ext)
	ext = strings.TrimPrefix(ext, ".")
	// 读取本地图片
	file := env.Env.ImageFile(filename, ext)
	_, err := os.Stat(file)
	if err != nil {
		if os.IsNotExist(err) {
			// 解析远程图片地址
			data, err := base64.RawURLEncoding.DecodeString(filename)
			if err != nil {
				ctx.AbortWithError(200, err)
				return
			}
			var uri = string(data) + "." + ext
			if !strings.HasPrefix(uri, "http") {
				uri = env.Env.RemoteImageHost + "/" + uri
			}
			resp, err := http.Get(uri)
			if err != nil {
				ctx.AbortWithError(200, err)
				return
			}
			defer resp.Body.Close()
			data, err = ioutil.ReadAll(resp.Body)
			if err != nil {
				ctx.AbortWithError(200, err)
				return
			}
			// 保存远程图片到本地
			err = ioutil.WriteFile(file, data, os.ModePerm)
			if err != nil {
				ctx.AbortWithError(200, err)
				return
			}
		} else {
			ctx.AbortWithError(200, err)
			return
		}
	}
	// 服务本地图片
	http.ServeFile(ctx.Writer, ctx.Request, file)
}

func LocalImageService(ctx *gin.Context) {
	services.ImgService.ServeHTTP(ctx.Writer, ctx.Request)
}

func GetImageUrl(ctx *gin.Context) {
	file := ctx.Query("file")
	uri, err := services.ImgStore.GetServeUrl(file)
	if err != nil {
		ctx.AbortWithError(500, err)
	} else {
		ctx.JSON(200, gin.H{"url": uri})
	}
}

func LocalImageProxy(ctx *gin.Context) {
	file := ctx.Query("file")
	uri, err := services.ImgStore.GetServeUrl(file)
	if err != nil {
		ctx.AbortWithError(500, err)
	} else {
		ctx.Redirect(301, uri)
	}
}

func CreateImageLabel(ctx *gin.Context) {
	label := &models.Label{}
	err := ctx.Bind(label)
	if err != nil {
		ctx.AbortWithError(500, err)
	} else {
		err = CreateLabel(label, label.Key)
		if err != nil {
			ctx.JSON(200, NewMessage("warning", err.Error()))
		} else {
			ctx.JSON(200, gin.H{"label": label})
		}
	}
}

func ImageProxy(ctx *gin.Context) {
	uri := ctx.Query("url")
	req, err := http.NewRequest("GET", uri, ctx.Request.Body)
	if err != nil {
		http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
	} else {
		for key, vs := range ctx.Request.Header {
			for _, v := range vs {
				req.Header.Add(key, v)
			}
		}
		req.Header.Del("Referer")
		req.Header.Del("Host")
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			ctx.Error(err)
		} else {
			ctx.Writer.WriteHeader(res.StatusCode)
			for key, vs := range res.Header {
				for _, v := range vs {
					ctx.Writer.Header().Add(key, v)
				}
			}
			_, err := io.Copy(ctx.Writer, res.Body)
			if err != nil {
				http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
			}
		}
	}
}

func ImageLabels(ctx *gin.Context) {
	labels := GetLabels()
	ctx.JSON(200, gin.H{"labels": labels})
}

func UpdateImageLabel(ctx *gin.Context) {
	label := &models.Label{}
	err := ctx.Bind(label)
	if err != nil {
		ctx.AbortWithError(500, err)
	} else {
		err = UpdateLabel(label)
		if err != nil {
			ctx.JSON(200, NewMessage("warning", err.Error()))
		} else {
			ctx.JSON(200, gin.H{"label": label})
		}
	}
}

func DeleteImageLabel(ctx *gin.Context) {
	label := &models.Label{}
	err := ctx.Bind(label)
	if err != nil {
		ctx.AbortWithError(500, err)
	} else {
		err = DeleteLabel(label)
		if err != nil {
			ctx.JSON(200, NewMessage("warning", err.Error()))
		} else {
			ctx.JSON(200, gin.H{"label": label})
		}
	}
}

func CountLabelImages(ctx *gin.Context) {
	key := ctx.Query("LabelKey")
	total := CountLabelImage(key)
	ctx.JSON(200, gin.H{"total": total})
}

type getLabelImages struct {
	LabelKey string `required:""`
	Limit    int    `num:"1-500"`
	Offset   int
}

func GetLabelImages(ctx *gin.Context) {
	p := &getLabelImages{}
	err := ctx.Bind(p)
	if err != nil {
		ctx.AbortWithError(200, err)
	} else {
		imgs := GetImages(p.LabelKey, p.Limit, p.Offset)
		for _, img := range imgs {
			img.Url, err = services.ImgStore.GetServeUrl(img.File)
			if err != nil {
				ctx.AbortWithError(500, err)
				return
			}
		}
		ctx.JSON(200, gin.H{"images": imgs})
	}
}

func UploadImages(ctx *gin.Context) {
	form, err := ctx.MultipartForm()
	if err != nil {
		ctx.AbortWithError(500, err)
	} else {
		key := url.Values(form.Value).Get("LabelKey")
		var save = func(header *multipart.FileHeader) (filename string, err error) {
			src, err := header.Open()
			if err != nil {
				return "", err
			}
			defer src.Close()
			data, err := ioutil.ReadAll(src)
			if err != nil {
				return "", err
			}
			ext := filepath.Ext(header.Filename)
			filename = newFileName(ext)
			err = services.ImgStore.Write(filename, data)
			if err != nil {
				return "", err
			} else {
				return filename, nil
			}
		}
		var uploadFiles []string
		for _, header := range form.File["Image"] {
			uploadFile, err := save(header)
			if err != nil {
				ctx.JSON(500, NewMessage("warning", err.Error()))
				return
			} else {
				uploadFiles = append(uploadFiles, uploadFile)
			}
		}
		var imgs []*models.Image
		// 创建新图片
		for _, uploadFile := range uploadFiles {
			img := &models.Image{LabelKey: key, File: uploadFile}
			err := CreateImage(img)
			if err != nil {
				ctx.JSON(200, NewMessage("warning", err.Error()))
				return
			} else {
				img.Url, err = services.ImgStore.GetServeUrl(uploadFile)
				if err != nil {
					ctx.JSON(200, NewMessage("warning", err.Error()))
					return
				}
				imgs = append(imgs, img)
			}
		}
		ctx.JSON(200, gin.H{"images": imgs})
	}
}

func DeleteLabelImages(ctx *gin.Context) {
	var IDs = ctx.QueryArray("IDs")
	exists := GetImagesByIDs(IDs)
	for _, img := range exists {
		err := services.ImgStore.Remove(img.File)
		if err != nil {
			ctx.JSON(200, NewMessage("warning", err.Error()))
			return
		}
	}
	err := DeleteImages(IDs)
	if err != nil {
		ctx.JSON(200, NewMessage("warning", err.Error()))
	} else {
		ctx.JSON(200, NewMessage("success", "已删除"))
	}

}

func CountLabelImage(labelKey string) (totalImage int64) {
	services.Gorm.Model(&models.Image{}).Where("label_key=?", labelKey).Count(&totalImage)
	return
}

func GetImageByFile(file string) *models.Image {
	exist := &models.Image{}
	services.Gorm.Where("file=?", file).First(exist)
	if exist.ID > 0 {
		return exist
	} else {
		return nil
	}
}

func GetImages(labelKey string, limit, offset int) (imgs []*models.Image) {
	services.Gorm.Where("label_key=?", labelKey).Limit(limit).Offset(offset).Order("id desc").Find(&imgs)
	return imgs
}

func CreateImage(i *models.Image) error {
	now := time.Now()
	i.CreatedAt = &now
	return services.Gorm.Create(i).Error
}

func DeleteImagesByFiles(files []string) error {
	for _, file := range files {
		err := os.Remove(file)
		if err != nil {
			if !os.IsNotExist(err) {
				return err
			}
		}
	}
	return services.Gorm.Where("file in (?)", files).Delete(&models.Image{}).Error
}

func DeleteImages(ids []string) error {
	return services.Gorm.Where("id in (?)", ids).Delete(&models.Image{}).Error
}

func GetImagesByIDs(ids []string) []*models.Image {
	var exists []*models.Image
	services.Gorm.Where("id in (?)", ids).Find(&exists)
	return exists
}

func GetLabel(id int) *models.Label {
	l := &models.Label{}
	services.Gorm.Model(&models.Label{ID: id}).Where("id=?", id).First(l)
	if l.ID > 0 {
		return l
	} else {
		return nil
	}
}

func GetLabels() (ls []*models.Label) {
	services.Gorm.Find(&ls)
	return ls
}

func CreateLabel(l *models.Label, key string) error {
	if l.AllowedEdit == 0 {
		l.AllowedEdit = models.False
	}
	exist := GetLabelByKey(key)
	if exist != nil {
		return ErrLabelKeyIsExist
	} else {
		l.Key = key
	}
	return services.Gorm.Create(l).Error
}

func UpdateLabel(l *models.Label) error {
	return services.Gorm.Model(l).Where("id=?", l.ID).Updates(l).Error
}

func DeleteLabel(l *models.Label) error {
	totalImage := CountLabelImage(l.Key)
	if totalImage > 0 {
		return ErrLabelIsNotEmpty
	} else {
		return services.Gorm.Delete(l).Error
	}
}

func GetLabelByKey(key string) *models.Label {
	exist := &models.Label{}
	services.Gorm.Where("key=?", key).First(exist)
	if exist.ID > 0 {
		return exist
	} else {
		return nil
	}
}

func InitLabel(labels ...*models.Label) {
	for _, label := range labels {
		exist := GetLabelByKey(label.Key)
		if exist == nil {
			err := CreateLabel(label, label.Key)
			if err != nil {
				panic(err)
			}
		} else {
			*label = *exist
		}
	}
}

func newFileName(ext string) string {
	now := time.Now()
	name := strconv.FormatInt(now.UnixNano(), 10)
	return now.Format("2006-01-02") + "-" + name + ext
}
